{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 安装类库\n",
    "# !mkdir /home/aistudio/external-libraries\n",
    "# !pip install imgaug -t /home/aistudio/external-libraries\n",
    "import sys\n",
    "sys.path.append('/home/aistudio/external-libraries')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: (32, 32, 3)\n",
      "label value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 读取数据\n",
    "reader = paddle.batch(\n",
    "    paddle.dataset.cifar.train10(),\n",
    "    batch_size=8) # 数据集读取器\n",
    "data = next(reader()) # 读取数据\n",
    "index = 4 # 批次索引\n",
    "\n",
    "# 读取图像\n",
    "image = np.array([x[0] for x in data]).astype(np.float32) # 读取图像数据，数据类型为float32\n",
    "image = image * 255 # 从[0,1]转换到[0,255]\n",
    "image = image[index].reshape((3, 32, 32)).transpose((1, 2, 0)).astype(np.uint8) # 数据格式从CHW转换为HWC，数据类型转换为uint8\n",
    "print('image shape:', image.shape)\n",
    "\n",
    "# 图像增强\n",
    "# sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "# seq = iaa.Sequential([\n",
    "#     sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "#     iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "# image = seq(image=image)\n",
    "\n",
    "# 读取标签\n",
    "label = np.array([x[1] for x in data]).astype(np.int64) # 读取标签数据，数据类型为int64\n",
    "vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "print('label value:', vlist[label[index]])\n",
    "\n",
    "# 显示图像\n",
    "image = Image.fromarray(image)   # 转换图像格式\n",
    "image.save('./work/out/img.png') # 保存读取图像\n",
    "plt.figure(figsize=(3, 3))       # 设置显示大小\n",
    "plt.imshow(image)                # 设置显示图像\n",
    "plt.show()                       # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n",
      "valid_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 训练数据增强\n",
    "def train_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 增强图像\n",
    "    sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "    seq = iaa.Sequential([\n",
    "        sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "        iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "    images = seq(images=images)\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 验证数据增强\n",
    "def valid_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 读取训练数据\n",
    "train_reader = paddle.batch(\n",
    "    paddle.reader.shuffle(paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "train_data = next(train_reader()) # 读取训练数据\n",
    "\n",
    "train_image = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取训练图像\n",
    "train_image = train_augment(train_image)                                                       # 训练图像增强\n",
    "train_label = np.array([x[1] for x in train_data]).reshape((-1, 1)).astype(np.int64)           # 读取训练标签\n",
    "print('train_data: image shape {}, label shape:{}'.format(train_image.shape, train_label.shape))\n",
    "\n",
    "# 读取验证数据\n",
    "valid_reader = paddle.batch(\n",
    "    paddle.dataset.cifar.test10(),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "valid_data = next(valid_reader()) # 读取验证数据\n",
    "\n",
    "valid_image = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取验证图像\n",
    "valid_image = valid_augment(valid_image)                                                       # 验证图像增强\n",
    "valid_label = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64)           # 读取验证标签\n",
    "print('valid_data: image shape {}, label shape:{}'.format(valid_image.shape, valid_label.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear, BatchNorm\n",
    "import math\n",
    "\n",
    "# 模组结构：输入维度，输出维度，滑动步长，基础长度, 队列长度\n",
    "group_arch = [(3, 128, 1, 2, 3), (512, 256, 2, 2, 3), (1024, 512, 2, 2, 3)]\n",
    "group_dim  = 2048 # 模组输出维度\n",
    "class_dim  = 10   # 类别数量维度\n",
    "\n",
    "# 卷积单元\n",
    "class ConvUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=3, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化卷积单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ConvUnit, self).__init__()\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=filter_size,\n",
    "            stride=stride,\n",
    "            padding=(filter_size-1)//2,                       # 输出特征图大小不变\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 投影单元\n",
    "class ProjUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=1, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化投影单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ProjUnit, self).__init__()\n",
    "        \n",
    "        # 添加池化\n",
    "        self.pool = Pool2D(\n",
    "            pool_size=filter_size,\n",
    "            pool_stride=stride,\n",
    "            pool_padding=0,\n",
    "            pool_type='avg')\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=1,\n",
    "            stride=1,\n",
    "            padding=0,\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行池化卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行池化\n",
    "        x = self.pool(x)\n",
    "        \n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "# 队列结构\n",
    "class SSRQueue(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, queues=2, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化队列结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长，1保持不变，2下采样\n",
    "            queues  - 队列长度，分割尺度为2^(n-1)\n",
    "            act     - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(SSRQueue, self).__init__()\n",
    "        \n",
    "        # 添加队列变量\n",
    "        self.queues = queues # 队列长度\n",
    "        self.split_list = [] # 分割列表\n",
    "        \n",
    "        # 添加队列列表\n",
    "        self.queue_list = [] # 队列列表\n",
    "        for i in range(queues):\n",
    "            # 添加队列项目\n",
    "            queue_item = self.add_sublayer( # 构造队列项目\n",
    "                'queue_' + str(i),\n",
    "                ConvUnit(\n",
    "                    in_dim=(in_dim if i==0 else out_dim), # 每组队列项目除第一个外，in_dim=out_dim\n",
    "                    out_dim=out_dim,\n",
    "                    filter_size=3,\n",
    "                    stride=(stride if i==0 else 1), # 每组队列项目除第一块外，stride=1\n",
    "                    act=act))\n",
    "            self.queue_list.append(queue_item) # 添加队列项目\n",
    "            \n",
    "            # 计算输出维度\n",
    "            if i < (queues-1): # 如果不是最后一项\n",
    "                out_dim = out_dim//2 # 输出维度减半\n",
    "                self.split_list.append(out_dim) # 添加分割列表\n",
    "            \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x_list = [] # 队列输出列表\n",
    "        for i, queue_item in enumerate(self.queue_list):\n",
    "            if i < (self.queues-1): # 如果不是最后一项\n",
    "                x = queue_item(x) # 提取队列特征\n",
    "                x_item, x = fluid.layers.split(input=x, num_or_sections=[-1, self.split_list[i]], dim=1)\n",
    "                x_list.append(x_item) # 添加输出列表\n",
    "            else: # 否则不对特征分割\n",
    "                x = queue_item(x) # 提取队列特征\n",
    "                x_list.append(x) # 添加输出列表\n",
    "        \n",
    "        # 联结特征\n",
    "        x = fluid.layers.concat(input=x_list, axis=1) # 队列输出列表按通道维进行特征联结\n",
    "        \n",
    "        return x\n",
    "    \n",
    "# 基础结构\n",
    "class SSRBasic(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, queues=1, is_pass=True):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化基础结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            queues  - 队列长度\n",
    "            is_pass - 是否直连\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(SSRBasic, self).__init__()\n",
    "        \n",
    "        # 是否直连标识\n",
    "        self.is_pass = is_pass\n",
    "        \n",
    "        # 添加投影路径\n",
    "        self.proj = ProjUnit(in_dim=in_dim, out_dim=out_dim*4, filter_size=stride, stride=stride, act=None)\n",
    "        \n",
    "        # 添加卷积路径\n",
    "        self.con1 = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=1, stride=1, act='relu')\n",
    "        \n",
    "        if queues==1:\n",
    "            self.con2 = ConvUnit(in_dim=out_dim, out_dim=out_dim, filter_size=3, stride=stride, act='relu')\n",
    "        else:\n",
    "            self.con2 = SSRQueue(in_dim=out_dim, out_dim=out_dim, stride=stride, queues=queues, act='relu')\n",
    "        \n",
    "        self.con3 = ConvUnit(in_dim=out_dim, out_dim=out_dim*4, filter_size=1, stride=1, act=None)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "            y - 输出特征\n",
    "        \"\"\"\n",
    "        # 直连路径\n",
    "        if self.is_pass: # 是否直连\n",
    "            x_pass = x\n",
    "        else:            # 否则投影\n",
    "            x_pass = self.proj(x)\n",
    "        \n",
    "        # 卷积路径\n",
    "        x_con1 = self.con1(x)      # 特征降维\n",
    "        x_con2 = self.con2(x_con1) # 特征提取\n",
    "        x_con3 = self.con3(x_con2) # 特征升维\n",
    "        \n",
    "        # 输出特征\n",
    "        x = fluid.layers.elementwise_add(x=x_pass, y=x_con3, act='relu') # 直连路径与卷积路径进行特征相加\n",
    "        y = x\n",
    "        \n",
    "        return x, y\n",
    "    \n",
    "# 模块结构\n",
    "class SSRBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, basics=1, queues=1):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模块结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            basics  - 基础长度\n",
    "            queues  - 队列长度\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(SSRBlock, self).__init__()\n",
    "        \n",
    "        # 添加模块列表\n",
    "        self.block_list = [] # 模块列表\n",
    "        for i in range(basics):\n",
    "            block_item = self.add_sublayer( # 构造模块项目\n",
    "                'block_' + str(i),\n",
    "                SSRBasic(\n",
    "                    in_dim=(in_dim if i==0 else out_dim*4), # 每组模块项目除第一块外，输入维度=输出维度\n",
    "                    out_dim=out_dim,\n",
    "                    stride=(stride if i==0 else 1), # 每组模块项目除第一块外，stride=1\n",
    "                    queues=queues,\n",
    "                    is_pass=(False if i==0 else True))) # 每组模块项目除第一块外，is_pass=True\n",
    "            self.block_list.append(block_item) # 添加模块项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "            y_list - 输出特征列表\n",
    "        \"\"\"\n",
    "        y_list = [] # 模块输出列表\n",
    "        for block_item in self.block_list:\n",
    "            x, y_item = block_item(x) # 提取模块特征\n",
    "            y_list.append(y_item) # 添加输出列表\n",
    "            \n",
    "        return x, y_list\n",
    "\n",
    "# 模组结构\n",
    "class SSRGroup(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模组结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(SSRGroup, self).__init__()\n",
    "        \n",
    "        # 添加模组列表\n",
    "        self.group_list = [] # 模组列表\n",
    "        for i, block_arch in enumerate(group_arch):\n",
    "            group_item = self.add_sublayer( # 构造模组项目\n",
    "                'group_' + str(i),\n",
    "                SSRBlock(\n",
    "                    in_dim=block_arch[0],\n",
    "                    out_dim=block_arch[1],\n",
    "                    stride=block_arch[2],\n",
    "                    basics=block_arch[3],\n",
    "                    queues=block_arch[4]))\n",
    "            self.group_list.append(group_item) # 添加模组项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "            y_list - 输出特征列表\n",
    "        \"\"\"\n",
    "        y_list = [] # 模组输出列表\n",
    "        for group_item in self.group_list:\n",
    "            x, y_item = group_item(x) # 提取模组特征\n",
    "            y_list.append(y_item) # 添加输出列表\n",
    "            \n",
    "        return x, y_list\n",
    "        \n",
    "# 分割网络\n",
    "class SSRNet(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化分割网络，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(SSRNet, self).__init__()\n",
    "        \n",
    "        # 添加模组结构\n",
    "        self.backbone = SSRGroup() # 输出：N*C*H*W\n",
    "        \n",
    "        # 添加全连接层\n",
    "        self.pool = Pool2D(global_pooling=True, pool_type='avg') # 输出：N*C*1*1\n",
    "        \n",
    "        stdv = 1.0/(math.sqrt(group_dim)*1.0)                    # 设置均匀分布权重方差\n",
    "        self.fc = Linear(                                        # 输出：=N*10\n",
    "            input_dim=group_dim,\n",
    "            output_dim=class_dim,\n",
    "            param_attr=fluid.initializer.Uniform(-stdv, stdv),   # 使用均匀分布初始权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),           # 使用常量数值初始偏置\n",
    "            act='softmax')\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入图像进行分类\n",
    "        输入:\n",
    "            x - 输入图像\n",
    "        输出:\n",
    "            x - 预测结果\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x, y_list = self.backbone(x)\n",
    "        \n",
    "        # 进行预测\n",
    "        x = self.pool(x)\n",
    "        x = fluid.layers.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tatol param: 21138570\n",
      "infer shape: [1, 10]\n"
     ]
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.base import to_variable\n",
    "import numpy as np\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 输入数据\n",
    "    x = np.random.randn(1, 3, 32, 32).astype(np.float32)\n",
    "    x = to_variable(x)\n",
    "    \n",
    "    # 进行预测\n",
    "    backbone = SSRNet() # 设置网络\n",
    "    \n",
    "    infer = backbone(x) # 进行预测\n",
    "    \n",
    "    # 显示结果\n",
    "    parameters = 0\n",
    "    for p in backbone.parameters():\n",
    "        parameters += np.prod(p.shape) # 统计参数\n",
    "    \n",
    "    print('tatol param:', parameters)\n",
    "    print('infer shape:', infer.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete - train time: 24486s, best epoch: 283, best loss: 0.176126, best accuracy: 96.18%\r"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.utils.plot import Ploter\n",
    "import numpy as np\n",
    "import time\n",
    "import math\n",
    "import os\n",
    "\n",
    "epoch_num = 300   # 训练周期，取值一般为[1,300]\n",
    "train_batch = 128 # 训练批次，取值一般为[1,256]\n",
    "valid_batch = 128 # 验证批次，取值一般为[1,256]\n",
    "displays = 100    # 显示迭代\n",
    "\n",
    "start_lr = 0.00001                         # 开始学习率，取值一般为[1e-8,5e-1]\n",
    "based_lr = 0.1                             # 基础学习率，取值一般为[1e-8,5e-1]\n",
    "epoch_iters = math.ceil(50000/train_batch) # 每轮迭代数\n",
    "warmup_iter = 10 * epoch_iters             # 预热迭代数，取值一般为[1,10]\n",
    "\n",
    "momentum = 0.9     # 优化器动量\n",
    "l2_decay = 0.00005 # 正则化系数，取值一般为[1e-5,5e-4]\n",
    "epsilon = 0.05     # 标签平滑率，取值一般为[1e-2,1e-1]\n",
    "\n",
    "checkpoint = True                    # 断点标识\n",
    "model_path = './work/out/ssrnet'     # 模型路径\n",
    "result_txt = './work/out/result.txt' # 结果文件\n",
    "class_num  = 10                      # 类别数量\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 准备数据\n",
    "    train_reader = paddle.batch(\n",
    "        reader=paddle.reader.shuffle(reader=paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "        batch_size=train_batch)\n",
    "    \n",
    "    valid_reader = paddle.batch(\n",
    "        reader=paddle.dataset.cifar.test10(),\n",
    "        batch_size=valid_batch)\n",
    "    \n",
    "    # 声明模型\n",
    "    model = SSRNet()\n",
    "    \n",
    "    # 优化算法\n",
    "    consine_lr = fluid.layers.cosine_decay(based_lr, epoch_iters, epoch_num) # 余弦衰减策略\n",
    "    decayed_lr = fluid.layers.linear_lr_warmup(consine_lr, warmup_iter, start_lr, based_lr) # 线性预热策略\n",
    "    \n",
    "    optimizer = fluid.optimizer.Momentum(\n",
    "        learning_rate=decayed_lr,                           # 衰减学习策略\n",
    "        momentum=momentum,                                  # 优化动量系数\n",
    "        regularization=fluid.regularizer.L2Decay(l2_decay), # 正则衰减系数\n",
    "        parameter_list=model.parameters())\n",
    "    \n",
    "    # 加载断点\n",
    "    if checkpoint: # 是否加载断点文件\n",
    "        model_dict, optimizer_dict = fluid.load_dygraph(model_path) # 加载断点参数\n",
    "        model.set_dict(model_dict)                                  # 设置权重参数\n",
    "        optimizer.set_dict(optimizer_dict)                          # 设置优化参数\n",
    "    else:          # 否则删除结果文件\n",
    "        if os.path.exists(result_txt): # 如果存在结果文件\n",
    "            os.remove(result_txt)      # 那么删除结果文件\n",
    "    \n",
    "    # 初始训练\n",
    "    avg_train_loss = 0 # 平均训练损失\n",
    "    avg_valid_loss = 0 # 平均验证损失\n",
    "    avg_valid_accu = 0 # 平均验证精度\n",
    "    \n",
    "    iterator = 1                                # 迭代次数\n",
    "    train_prompt = \"Train loss\"                 # 训练标签\n",
    "    valid_prompt = \"Valid loss\"                 # 验证标签\n",
    "    ploter = Ploter(train_prompt, valid_prompt) # 训练图像\n",
    "    \n",
    "    best_epoch = 0           # 最好周期\n",
    "    best_accu = 0            # 最好精度\n",
    "    best_loss = 100.0        # 最好损失\n",
    "    train_time = time.time() # 训练时间\n",
    "    \n",
    "    # 开始训练\n",
    "    for epoch_id in range(epoch_num):\n",
    "        # 训练模型\n",
    "        model.train() # 设置训练\n",
    "        for batch_id, train_data in enumerate(train_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = train_augment(image_data)                                                        # 使用数据增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "\n",
    "            label_data = np.array([x[1] for x in train_data]).astype(np.int64)                        # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                             # 转换数据类型\n",
    "            label = fluid.layers.label_smooth(label=fluid.one_hot(label, class_num), epsilon=epsilon) # 使用标签平滑\n",
    "            label.stop_gradient = True                                                                # 停止梯度传播\n",
    "\n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label, soft_label=True)\n",
    "            train_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            # 反向传播\n",
    "            train_loss.backward()\n",
    "            optimizer.minimize(train_loss)\n",
    "            model.clear_gradients()\n",
    "            \n",
    "            # 显示结果\n",
    "            if iterator % displays == 0:\n",
    "                # 显示图像\n",
    "                avg_train_loss = train_loss.numpy()[0]                # 设置训练损失\n",
    "                ploter.append(train_prompt, iterator, avg_train_loss) # 添加训练图像\n",
    "                ploter.plot()                                         # 显示训练图像\n",
    "                \n",
    "                # 打印结果\n",
    "                print(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\".format(\n",
    "                    iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "                \n",
    "                # 写入文件\n",
    "                with open(result_txt, 'a') as file:\n",
    "                    file.write(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\\n\".format(\n",
    "                        iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "            \n",
    "            # 增加迭代\n",
    "            iterator += 1\n",
    "            \n",
    "        # 验证模型\n",
    "        valid_loss_list = [] # 验证损失列表\n",
    "        valid_accu_list = [] # 验证精度列表\n",
    "        \n",
    "        model.eval()   # 设置验证\n",
    "        for batch_id, valid_data in enumerate(valid_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = valid_augment(image_data)                                                        # 使用图像增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "            \n",
    "            label_data = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64) # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                       # 转换数据类型\n",
    "            label.stop_gradient = True                                                          # 停止梯度传播\n",
    "            \n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算精度\n",
    "            valid_accu = fluid.layers.accuracy(infer,label)\n",
    "            \n",
    "            valid_accu_list.append(valid_accu.numpy())\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label)\n",
    "            valid_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            valid_loss_list.append(valid_loss.numpy())\n",
    "        \n",
    "        # 设置结果\n",
    "        avg_valid_accu = np.mean(valid_accu_list)             # 设置验证精度\n",
    "        \n",
    "        avg_valid_loss = np.mean(valid_loss_list)             # 设置验证损失\n",
    "        ploter.append(valid_prompt, iterator, avg_valid_loss) # 添加训练图像\n",
    "        \n",
    "        # 保存模型\n",
    "        fluid.save_dygraph(model.state_dict(), model_path)     # 保存权重参数\n",
    "        fluid.save_dygraph(optimizer.state_dict(), model_path) # 保存优化参数\n",
    "        \n",
    "        if avg_valid_loss < best_loss:\n",
    "            fluid.save_dygraph(model.state_dict(), model_path + '-best') # 保存权重\n",
    "            \n",
    "            best_epoch = epoch_id + 1                                    # 更新迭代\n",
    "            best_accu = avg_valid_accu                                   # 更新精度\n",
    "            best_loss = avg_valid_loss                                   # 更新损失\n",
    "    \n",
    "    # 显示结果\n",
    "    train_time = time.time() - train_time # 设置训练时间\n",
    "    print('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}'.format(\n",
    "        train_time, best_epoch, best_loss, best_accu))\n",
    "    \n",
    "    # 写入文件\n",
    "    with open(result_txt, 'a') as file:\n",
    "        file.write('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}\\n'.format(\n",
    "            train_time, best_epoch, best_loss, best_accu))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "infer time: 0.014069s, infer value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import time\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image_path = './work/out/img.png' # 图片路径\n",
    "model_path = './work/out/ssrnet-best' # 模型路径\n",
    "\n",
    "# 加载图像\n",
    "def load_image(image_path):\n",
    "    \"\"\"\n",
    "    功能:\n",
    "        读取图像并转换到输入格式\n",
    "    输入:\n",
    "        image_path - 输入图像路径\n",
    "    输出:\n",
    "        image - 输出图像\n",
    "    \"\"\"\n",
    "    # 读取图像\n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    \n",
    "    # 转换格式\n",
    "    image = image.resize((32, 32), Image.ANTIALIAS) # 调整图像大小\n",
    "    image = np.array(image, dtype=np.float32) # 转换数据格式，数据类型转换为float32\n",
    "\n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    image = (image/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    image = image.transpose((2, 0, 1)).astype(np.float32) # 数据格式从HWC转换为CHW，数据类型转换为float32\n",
    "    \n",
    "    # 增加维度\n",
    "    image = np.expand_dims(image, axis=0) # 增加数据维度\n",
    "    \n",
    "    return image\n",
    "\n",
    "# 预测图像\n",
    "with fluid.dygraph.guard():\n",
    "    # 读取图像\n",
    "    image = load_image(image_path)\n",
    "    image = fluid.dygraph.to_variable(image)\n",
    "    \n",
    "    # 加载模型\n",
    "    model = SSRNet()                               # 加载模型\n",
    "    model_dict, _ = fluid.load_dygraph(model_path) # 加载权重\n",
    "    model.set_dict(model_dict)                     # 设置权重\n",
    "    model.eval()                                   # 设置验证\n",
    "    \n",
    "    # 前向传播\n",
    "    infer_time = time.time()              # 推断开始时间\n",
    "    infer = model(image)\n",
    "    infer_time = time.time() - infer_time # 推断结束时间\n",
    "    \n",
    "    # 显示结果\n",
    "    vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "    print('infer time: {:f}s, infer value: {}'.format(infer_time, vlist[np.argmax(infer.numpy())]) )\n",
    "    \n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    plt.figure(figsize=(3, 3))     # 设置显示大小\n",
    "    plt.imshow(image)              # 设置显示图像\n",
    "    plt.show()                     # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.8.4 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
